from datetime import timedelta

from django.db import transaction
from django.utils.translation import gettext_lazy, gettext as _
from django.utils import timezone as django_timezone
from rest_framework.permissions import IsAuthenticated
from rest_framework.serializers import Serializer
from rest_framework.response import Response
from rest_framework.decorators import action
from drf_yasg.utils import swagger_auto_schema, no_body
from drf_yasg import openapi
import pydantic

from django.db.models import TextChoices

from core import errors
from utils.lazy_funcs import lazy_str_join
from apps.api.paginations import NewPageNumberPagination100
from apps.api.viewsets import NormalGenericViewSet, serializer_error_msg
from apps.app_tx_meeting import serializers
from apps.app_tx_meeting.managers import (
    MeetingReservationManager, MeetingUserWrapper, ReservationNoShowManager, NoShowWhitelistManager,
    tx_meeting_configs
)
from apps.app_tx_meeting.models import MeetingReservation, MeetingRoom, MeetingRoomType
from apps.app_tx_meeting.tx_clients.client import TxMeetingClient
from apps.app_tx_meeting.tx_clients import inputs as tx_inputs
from apps.app_tx_meeting import builders
from apps.app_tx_meeting.helpers import (
    MeetReservationHelper,RoomReserveTimeIntervalCounter, TimeLine,
    UTCAdd8TimeHelper
)
from apps.app_tx_meeting.handlers import CancelReservationHandler


class ReservationOrderBy(TextChoices):
    ASC_START_TIME = 'start_time', gettext_lazy('会议开始时间正序')
    DESC_START_TIME = '-start_time', gettext_lazy('会议开始时间倒序')
    ASC_END_TIME = 'end_time', gettext_lazy('会议结束时间正序')
    DESC_END_TIME = '-end_time', gettext_lazy('会议结束时间倒序')


class MeetingRoomReservationViewSet(NormalGenericViewSet):
    permission_classes = [IsAuthenticated]
    pagination_class = NewPageNumberPagination100
    lookup_field = 'id'

    @swagger_auto_schema(
        tags=['tx_meeting'],
        operation_summary=gettext_lazy('会议预定'),
        responses={
            200: ''''''
        }
    )
    def create(self, request, *args, **kwargs):
        """
        会议预定

            * 腾讯会议的创建人和会议预定人必须是同一个用户，管理员除外，管理员可以帮其他用户预定
            * 普通用户可约7日内会议，管理员可约30日内会议

            http code 200:
            {
                "id": "ce575ibaacun1dpehdk9n8jjb",
                "subject": "test",
                "start_time": 1745740800,
                "end_time": 1745744400,
                "room_reserve_start": 1745739720,
                "room_reserve_end": 1745755200,
                "people_num": 88,
                "room_type": 100,
                "status": "reserved",
                "creation_time": "2025-04-27T07:22:46.149281Z",
                "update_time": "2025-04-27T07:22:46.149461Z",
                "user": {
                    "id": "8",
                    "username": "zhangsan@cnic.cn",
                    "full_name": "张三"
                },
                "tx_start_time": 1745735400,
                "tx_end_time": 1745739000,
                "tx_meeting_id": "10923254423200092415",
                "tx_meeting_userid": "wangyushun@cnic.cn",
                "tx_user_nick_name": "25级 张三",
                "tx_meeting_code": "70962983609",
                "tx_join_url": "https://meeting.tencent.com/dm/VGldjgrJebst",
                "room_reserve_status": "wait",
                "room_reserve_desc": "",
                "advance_notice_seconds": 3600,
                "email_notice_id": null
            }

            http code 400,401,403, 409:
            {
                "code": "TooManyReservations",
                "message": "会议预定数已达上限，按会议开始时间，同一天内的会议可预定3场，一周内的会议可预定10场。"
            }
            400 code:
                InvalidArgument: 参数相关错误
            403:
                AccessDenied: 限制类错误
            409:
                Conflict: 冲突类错误。比如 选择了普通用户不可见的会议室规格；会议室资源为0；腾讯会议的创建人和会议预定人必须是同一个用户
                TooManyReservations: 会议预定数已达上限
                NoShowRestrict: 由于您多次爽约，在北京时间xx之前不允许预定会议。
        """
        auth_user = request.user
        serializer = self.get_serializer(data=request.data)
        if not serializer.is_valid(raise_exception=False):
            msg = serializer_error_msg(serializer.errors)
            return self.exception_response(exc=errors.InvalidArgument(message=msg))

        data = serializer.validated_data
        tx_meeting_code = data['meeting_code']
        start_time = data['start_time']
        end_time = data['end_time']
        room_type = data['room_type']

        # 可预定7日内，至少30分钟后的会议
        if start_time <= int((django_timezone.now() + timedelta(minutes=30)).timestamp()):
            return self.exception_response(
                exc=errors.InvalidArgument(message=_('只可预定开始时间至少在30分钟后开始的会议。')))

        meeting_user = MeetingUserWrapper(user=auth_user)
        # 通用户可约7日内会议，管理员可约30日内会议
        toady_start_dt = UTCAdd8TimeHelper.today_start()
        after_7days_dt = toady_start_dt + timedelta(days=7)
        after_7days_ts = int(after_7days_dt.timestamp())

        if end_time > after_7days_ts:
            if not meeting_user.is_admin():
                return self.exception_response(
                    exc=errors.InvalidArgument(
                        message=_('只可预定7日内会议，预定时间必须在北京时间{time}零点之前。').format(
                            time=after_7days_dt.date().isoformat())))

            after_30days_dt = toady_start_dt + timedelta(days=30)
            if end_time > int(after_30days_dt.timestamp()):
                return self.exception_response(
                    exc=errors.InvalidArgument(
                        message=_('管理员可预定30日内的会议，预定时间必须在北京时间{time}零点之前。').format(
                            time=after_30days_dt.date().isoformat())))

        # 会议室规格，和可见性
        room_pools = MeetingRoom.get_main_instance(select_for_update=False)
        room_num, visible = room_pools.get_room_config(room_type=room_type)
        if room_num <= 0:
            return self.exception_response(
                errors.ConflictError(message=_('当前规格的会议室资源为0，请选择其他规格会议室。')))

        # 普通用户不可见，管理员可见
        if not visible:
            if not meeting_user.is_admin():
                return self.exception_response(
                    errors.ConflictError(message=_('你没有权限预定当前规格的会议室，请选择其他规格会议室。')))

        # 权限
        if not meeting_user.is_active_user():
            return self.exception_response(exc=errors.AccessDenied(message=_('你的账户无效，请联系管理员。')))

        # 会议预定数量限制，管理员除外
        if not meeting_user.is_admin():
            try:
                self.check_meeting_limit(meeting_start_ts=start_time, user=auth_user)
            except Exception as exc:
                return self.exception_response(exc=exc)

        noshow_penalty_enabled = tx_meeting_configs.to_bool(
            tx_meeting_configs.get(tx_meeting_configs.ConfigName.NO_SHOW_PENALTY_ENABLED.value)
        )
        if noshow_penalty_enabled:
            # 爽约限制，管理员和白名单用户除外
            if not meeting_user.is_admin() and not NoShowWhitelistManager.is_user_in_whitelist(username=auth_user.username):
                noshow_restrict = ReservationNoShowManager.get_noshow_restrict_time(username=auth_user.username)
                if noshow_restrict.end:
                    # 有爽约限制时，当前时间未过限制结束时间，不允许预定会议
                    if django_timezone.now() < noshow_restrict.end:
                        return self.exception_response(
                            exc=errors.ConflictError(
                                code='NoShowRestrict',
                                message=_('由于您多次爽约，在北京时间%(time)s之前不允许预定会议。') % {
                                    'time': noshow_restrict.end.strftime('%Y-%m-%d %H:%M:%S')}))

        # 通过会议号查询腾讯会议信息
        try:
            params = tx_inputs.ListMeetingsInput(
                meeting_state=3,
                meeting_code=tx_meeting_code
            )
        except pydantic.ValidationError as exc:
            msg = TxMeetingClient.get_msg_pydantic_error(exc)
            msg = _('通过会议号查询腾讯会议信息失败。') + msg
            return self.exception_response(exc=errors.InvalidArgument(message=msg))

        tx_client = builders.build_tx_meeting_client()
        r = tx_client.list_corp_meetings(params=params)
        if not r.ok:
            return self.exception_response(exc=r.error)

        if not r.data.meeting_info_list:
            return self.exception_response(
                exc=errors.InvalidArgument(message=_('通过会议号未查询到即将召开的腾讯会议信息，请核实腾讯会议号是否有误。')))

        meet_info = r.data.meeting_info_list[0]
        tx_meeting_id = meet_info.meeting_id
        tx_meeting_userid = meet_info.userid
        tx_user_nick_name = meet_info.nick_name
        tx_start_time = meet_info.start_time
        tx_end_time = meet_info.end_time

        # 腾讯会议的创建者和会议预定用户必须一致，管理员除外
        if tx_meeting_userid != auth_user.username:
            if not meeting_user.is_admin():
                return self.exception_response(
                    exc=errors.ConflictError(
                        message=_('你不是会议号对应的腾讯会议的创建人，腾讯会议的创建人和会议预定人必须是同一个用户')))

        # 通过id，腾讯会议详情查询
        try:
            detail_params = tx_inputs.GetMeetingDetailInput(
                meeting_id=tx_meeting_id, instanceid=5
            )
        except pydantic.ValidationError as exc:
            msg = TxMeetingClient.get_msg_pydantic_error(exc)
            msg = _('通过会议ID查询腾讯会议详情信息失败。') + msg
            return self.exception_response(exc=errors.InvalidArgument(message=msg))

        r = tx_client.get_meeting_detail(params=detail_params)
        if not r.ok:
            return self.exception_response(exc=r.error)

        if not r.data.meeting_info_list:
            return self.exception_response(
                exc=errors.InvalidArgument(message=_('通过会议号未查询到即将召开的腾讯会议信息，请核实腾讯会议号是否有误。')))

        tx_join_url = r.data.meeting_info_list[0].join_url

        # 通过会议预定有关默认配置，计算有关字段值
        room_reserve_start, room_reserve_end = MeetReservationHelper.room_reserve_start_end(
            start_time=start_time, end_time=end_time)
        advance_notice_time, advance_notice_seconds = MeetReservationHelper.meeting_notice_time_advance_seconds(
            start_time=start_time)

        # 事务锁，根据已预定记录查询是否有足够会议室资源
        with transaction.atomic():
            room_pools = MeetingRoom.get_main_instance(select_for_update=True)
            room_total_num = room_pools.get_room_num(room_type=room_type)
            if room_total_num <= 0:
                return self.exception_response(
                    errors.ConflictError(message=_('当前规格的会议室资源为0，请选择其他规格会议室。')))

            # 已存在会议号相同并且预定时间区间有重叠的会议预订记录
            same_code_overlapping_qs = MeetingReservationManager.get_overlapping_reservations(
                start=room_reserve_start, end=room_reserve_end, room_type=None, tx_meeting_code=tx_meeting_code)
            if same_code_overlapping_qs.exists():
                return self.exception_response(
                    errors.ConflictError(message=_('已存在会议号相同并且预定时间区间有重叠的会议预订记录。')))

            need_room_num = RoomReserveTimeIntervalCounter.meeting_room_reserved_count(
                query_interval=TimeLine(start=room_reserve_start, end=room_reserve_end),
                room_type=room_type,
                contain_query_interval=True
            )
            if need_room_num > room_total_num:
                return self.exception_response(
                    errors.ConflictError(message=_('会议室资源不足，请选择其他规格会议室，或者调整会议预定的时间区间。')))

            meeting_reservation = MeetingReservationManager.create_meeting_reservation(
                subject=data['subject'],
                start_time=start_time,
                end_time=end_time,
                people_num=0,
                room_type=room_type,
                room_reserve_start=room_reserve_start,
                room_reserve_end=room_reserve_end,
                status=MeetingReservation.Status.RESERVED.value,
                user=auth_user,
                tx_meeting_id=tx_meeting_id,
                tx_meeting_code=tx_meeting_code,
                tx_meeting_userid=tx_meeting_userid,
                tx_user_nick_name=tx_user_nick_name,
                tx_start_time=tx_start_time,
                tx_end_time=tx_end_time,
                tx_join_url=tx_join_url,
                room_reserve_status=MeetingReservation.RoomReserveStatus.WAIT.value,
                room_reserve_desc='',
                advance_notice_seconds=advance_notice_seconds,
                need_notice_time=advance_notice_time
            )

        return Response(
            data=serializers.MeetingReservationSerializer(meeting_reservation).data
        )

    @staticmethod
    def check_meeting_limit(meeting_start_ts: int, user):
        """
        会议预定数量限制

        一天内可预定3场会议，一周可预定10场会议

        :raises: ConflictError
        """
        week_start_ts, next_week_start_ts = UTCAdd8TimeHelper.get_week_range_ts(meeting_start_ts)
        # 会议开始时间在一周内的预定, 不含取消的
        reservation_qs = MeetingReservationManager.get_meeting_reservation_qs(
            user_id=user.id, start_time_gte=week_start_ts, start_time_lt=next_week_start_ts
        )
        reservation_qs = reservation_qs.exclude(status=MeetingReservation.Status.CANCELLED.value)

        day_start_ts, next_day_start_ts = UTCAdd8TimeHelper.get_day_range_ts(meeting_start_ts)
        day_reservations = []
        for obj in reservation_qs:
            if day_start_ts <= obj.start_time < next_day_start_ts:
                day_reservations.append(obj)

        if len(day_reservations) >= 3:
            raise errors.ConflictError(
                code='TooManyReservations',
                message=_('同一天内的会议预定数已达上限，按会议开始时间，同一天内的会议可预定3场，一周内的会议可预定10场。')
            )

        if len(reservation_qs) >= 10:
            raise errors.ConflictError(
                code='TooManyReservations',
                message=_('同一周内的会议预定数已达上限，按会议开始时间，同一天内的会议可预定3场，一周内的会议可预定10场。')
            )

    @swagger_auto_schema(
        tags=['tx_meeting'],
        operation_summary=gettext_lazy('列举会议预定'),
        manual_parameters=[
            openapi.Parameter(
                name='status',
                in_=openapi.IN_QUERY,
                type=openapi.TYPE_STRING,
                required=False,
                description=lazy_str_join(gettext_lazy('预定状态。'), MeetingReservation.Status.choices)
            ),
            openapi.Parameter(
                name='room_type',
                in_=openapi.IN_QUERY,
                type=openapi.TYPE_INTEGER,
                required=False,
                description=lazy_str_join(gettext_lazy('会议室类型。'), MeetingRoomType.choices)
            ),
            openapi.Parameter(
                name='meeting_code',
                in_=openapi.IN_QUERY,
                type=openapi.TYPE_STRING,
                required=False,
                description=gettext_lazy('会议号查询')
            ),
            openapi.Parameter(
                name='start_time_start',
                in_=openapi.IN_QUERY,
                type=openapi.TYPE_INTEGER,
                required=False,
                description=gettext_lazy('预定开始时间查询，查询区间的开始时间戳。')
            ),
            openapi.Parameter(
                name='start_time_end',
                in_=openapi.IN_QUERY,
                type=openapi.TYPE_INTEGER,
                required=False,
                description=gettext_lazy('预定开始时间查询，查询区间的结束时间戳。')
            ),
            openapi.Parameter(
                name='order_by',
                in_=openapi.IN_QUERY,
                type=openapi.TYPE_STRING,
                required=False,
                description=lazy_str_join(gettext_lazy('排序，默认按预定时间倒序。'), ReservationOrderBy.choices),
                enum=ReservationOrderBy.values
                # items=openapi.Items(type=openapi.TYPE_STRING, enum=ReservationOrderBy.values),
                # collectionFormat="multi",
            ),
            openapi.Parameter(
                name='meeting_userid',
                in_=openapi.IN_QUERY,
                type=openapi.TYPE_STRING,
                required=False,
                description=gettext_lazy('会议创建人账号查询，仅管理员查询有效')
            ),
        ] + NormalGenericViewSet.PARAMETERS_AS_ADMIN,
        responses={
            200: ''''''
        }
    )
    def list(self, request, *args, **kwargs):
        """
        列举会议预定记录，用户个人或管理员

            * 个人能查到自己预定的会议和别人帮自己预定的会议

            http code 200:
            {
                "count": 1,
                "page_num": 1,
                "page_size": 100,
                "results": [
                    {
                        "id": "ce575ibaacun1dpehdk9n8jjb",
                        "subject": "test",
                        "start_time": 1745740800,   # 用户预定的开始时间
                        "end_time": 1745744400, # 用户预定的结束时间
                        "room_reserve_start": 1745739720, # 会议室预留开始时间
                        "room_reserve_end": 1745755200, # 会议室预留结束时间
                        "people_num": 88,   # 会议人数
                        "room_type": 100,   # 会议室类型, 50/100/300/500/1000 方
                        "status": "reserved",       # reserved（已预定），cancelled（已取消）
                        "creation_time": "2025-04-27T07:22:46.149281Z",
                        "update_time": "2025-04-27T07:22:46.149461Z",
                        "user": {       # 预定用户
                            "id": "8",
                            "username": "zhangsan@cnic.cn",
                            "full_name": "张三"
                        },
                        "tx_start_time": 1745735400,    # 腾讯会议的开始时间
                        "tx_end_time": 1745739000,      # 腾讯会议的结束时间
                        "tx_meeting_id": "10923254423200092415",    # 腾讯会议的id
                        "tx_meeting_userid": "zhangsan@cnic.cn",  # 腾讯会议创建者
                        "tx_user_nick_name": "25级 张三",          # 腾讯会议创建者昵称
                        "tx_meeting_code": "70962983609",       # 腾讯会议号
                        "tx_join_url": "https://meeting.tencent.com/dm/VGldjgrJebst",   # 腾讯会议加入连接
                        "room_reserve_status": "wait",  # 会议室预留状态；wait(待预留)，unreserved(预留失败)，reserved(已预留)，unreleased(释放失败)，released(已释放)
                        "room_reserve_desc": "",    # 会议室预留状态描述
                        "advance_notice_seconds": 3600, # 发送会议会前提醒通知提前的秒数；开始会设置一个默认值，后续用户可以自行设置
                        "email_notice_id": null
                    }
                ]
            }
        """
        status = request.query_params.get('status')
        room_type = request.query_params.get('room_type')
        meeting_code = request.query_params.get('meeting_code')
        start_time_start = request.query_params.get('start_time_start')
        start_time_end = request.query_params.get('start_time_end')
        order_by = request.query_params.get('order_by')
        meeting_userid = request.query_params.get('meeting_userid')

        try:
            if status and status not in MeetingReservation.Status.values:
                raise errors.InvalidArgument(message=_('预定状态无效'))

            if room_type:
                try:
                    room_type = int(room_type)
                    if room_type not in MeetingRoomType.values:
                        raise  ValueError
                except ValueError:
                    raise errors.InvalidArgument(message=_('会议室类型无效'))

            if meeting_code:
                meeting_code = meeting_code.replace(' ', '')
                meeting_code = meeting_code.replace('-', '')

            if start_time_start:
                try:
                    start_time_start = int(start_time_start)
                except ValueError:
                    raise errors.InvalidArgument(message=_('查询区间的开始时间戳无效'))

            if start_time_end:
                try:
                    start_time_end = int(start_time_end)
                except ValueError:
                    raise errors.InvalidArgument(message=_('查询区间的结束时间戳无效'))

            if start_time_start and start_time_end:
                if start_time_end <= start_time_start:
                    raise errors.InvalidArgument(message=_('查询区间的结束时间必须大于开始时间'))

            if order_by and order_by not in ReservationOrderBy.values:
                raise errors.InvalidArgument(message=_('排序参数值无效'))
        except errors.Error as exc:
            return self.exception_response(exc=exc)

        try:
            if self.is_as_admin_request(request=request):
                mu = MeetingUserWrapper(user=request.user)
                if not mu.is_admin():
                    raise errors.AccessDenied(message=_('你没有管理员权限'))

                qs = MeetingReservationManager.get_meeting_reservation_qs(
                    user_id=None,
                    status=status, room_type=room_type,
                    start_time_gte=start_time_start, start_time_lte=start_time_end,
                    tx_meeting_code=meeting_code, meeting_userid=meeting_userid
                )
            else:
                qs = MeetingReservationManager.get_user_meeting_reservation_qs(
                    user=request.user,
                    status=status, room_type=room_type,
                    start_time_gte=start_time_start, start_time_lte=start_time_end,
                    tx_meeting_code=meeting_code
                )

            if order_by:
                qs = qs.order_by(order_by, '-creation_time')
            else:
                qs = qs.order_by('-creation_time')

            objs = self.paginate_queryset(qs)
            serializer = self.get_serializer(instance=objs, many=True)
            return self.get_paginated_response(serializer.data)
        except Exception as exc:
            return self.exception_response(exc)

    @swagger_auto_schema(
        tags=['tx_meeting'],
        operation_summary=gettext_lazy('取消会议预定'),
        request_body=no_body,
        manual_parameters=NormalGenericViewSet.PARAMETERS_AS_ADMIN,
        responses={
            200: ''''''
        }
    )
    @action(methods=['post'], detail=True, url_path='cancel', url_name='cancel')
    def cancel_room_reservation(self, request, *args, **kwargs):
        """
        取消会议室预定

            * 已过预定结束时间 无法取消

            http code 200: no content
        """
        reservation_id = kwargs[self.lookup_field]

        try:
            CancelReservationHandler.cancel_reservation(
                reservation_id=reservation_id,
                operator=request.user,
                is_as_admin=self.is_as_admin_request(request=request)
            )
        except errors.Error as exc:
            return self.exception_response(exc=exc)

        return Response(status=200)

    @swagger_auto_schema(
        tags=['tx_meeting'],
        operation_summary=gettext_lazy('查询会议预定详情'),
        manual_parameters=NormalGenericViewSet.PARAMETERS_AS_ADMIN,
        responses={
            200: ''''''
        }
    )
    def retrieve(self, request, *args, **kwargs):
        """
        查询会议预定详情，用户个人或管理员

            * 个人能查到自己预定的会议和别人帮自己预定的会议

            http code 200:
            {
                "id": "ce575ibaacun1dpehdk9n8jjb",
                "subject": "test",
                "start_time": 1745740800,
                "end_time": 1745744400,
                "room_reserve_start": 1745739720,
                "room_reserve_end": 1745755200,
                "people_num": 88,
                "room_type": 100,
                "status": "reserved",
                "creation_time": "2025-04-27T07:22:46.149281Z",
                "update_time": "2025-04-27T07:22:46.149461Z",
                "user": {
                    "id": "8",
                    "username": "zhangsan@cnic.cn",
                    "full_name": "张三"
                },
                "tx_start_time": 1745735400,
                "tx_end_time": 1745739000,
                "tx_meeting_id": "10923254423200092415",
                "tx_meeting_userid": "wangyushun@cnic.cn",
                "tx_user_nick_name": "25级 张三",          # 腾讯会议创建者昵称
                "tx_meeting_code": "70962983609",
                "tx_join_url": "https://meeting.tencent.com/dm/VGldjgrJebst",
                "room_reserve_status": "wait",
                "room_reserve_desc": "",
                "advance_notice_seconds": 3600,
                "email_notice": {       # 未发送会前提醒为null
                    "id": "6uvpn30r4oq1ns9pv526he9kq",
                    "subject": "test",
                    "message": "您有一个会议即将开始",
                    "is_html": false,   # 内容是否是html格式
                    "sender": "test@qq.com",
                    "receiver": "user1@qq.com",
                    "status": "success",    # wait(待发送)，success(发送成功)，failed(发送失败)
                    "status_desc": "",
                    "send_time": "2025-04-28T06:30:08.515307Z",
                    "success_time": "2025-04-28T06:33:16.901231Z"   # 发送成功时间，可能为null
                }
            }
        """
        res_id = kwargs[self.lookup_field]
        try:
            if self.is_as_admin_request(request=request):
                mu = MeetingUserWrapper(user=request.user)
                if not mu.is_admin():
                    raise errors.AccessDenied(message=_('你没有管理员权限'))

                obj = MeetingReservation.objects.select_related('user').filter(id=res_id).first()
                if obj is None:
                    raise errors.TargetNotExist(message=_('会议预定记录不存在'))
            else:
                obj = MeetingReservationManager.get_user_meeting_reservation(
                    _id=res_id, user=request.user)
        except errors.Error as exc:
            return self.exception_response(exc=exc)

        return Response(data=self.get_serializer(obj).data)

    @swagger_auto_schema(
        tags=['tx_meeting'],
        operation_summary=gettext_lazy('会议预定会前通知时间设置'),
        request_body=no_body,
        manual_parameters=[
            openapi.Parameter(
                name='advance_minutes',
                in_=openapi.IN_QUERY,
                type=openapi.TYPE_INTEGER,
                required=True,
                description=gettext_lazy('会前提醒通知发送提前分钟数；可选范围为15分钟到3天')
            )
        ],
        responses={
            200: ''''''
        }
    )
    @action(methods=['post'], detail=True, url_path='notice-time', url_name='notice-time')
    def set_notice_time(self, request, *args, **kwargs):
        """
        会议预定会前通知时间设置

            * 建议前端给用户提供一些固定选项：“15分钟”、“30分钟”、“1小时”、“3小时”、“8小时”、“1天”、“2天”、“3天”

            http code 200: no content
        """
        advance_minutes = request.query_params.get('advance_minutes')
        if not advance_minutes:
            return self.exception_response(
                errors.InvalidArgument(message=_('必须提交会前提醒通知发送提前时间')))

        try:
            advance_minutes = int(advance_minutes)
        except ValueError:
            return self.exception_response(
                errors.InvalidArgument(message=_('会前通知发送提前时间无效')))

        if advance_minutes < 15:
            return self.exception_response(
                errors.InvalidArgument(message=_('会前通知发送提前时间不能小于15分钟')))

        if advance_minutes > 60 * 24 * 3:
            return self.exception_response(
                errors.InvalidArgument(message=_('会前通知发送提前时间不能大于3天')))

        try:
            obj = MeetingReservationManager.get_user_meeting_reservation(
                _id=kwargs[self.lookup_field], user=request.user)
            MeetingReservationManager.set_reservation_notice_time(reservation=obj, advance_minutes=advance_minutes)
        except errors.Error as exc:
            return self.exception_response(exc=exc)

        return Response()

    def get_serializer_class(self):
        if self.action == 'list':
            return serializers.MeetingReservationSerializer
        elif self.action == 'create':
            return serializers.MeetingReservationCreateSerializer
        elif self.action == 'retrieve':
            return serializers.MeetingReservationDetailSerializer

        return Serializer
